#include <QCloseEvent>
#include <QDockWidget>
#include <QFileDialog>
#include <QMessageBox>
#include <QSignalMapper>
#include <QTableView>
#include <QUndoStack>

#include "FrakOutConstants.h"
#include "FrakOutController.h"
#include "FrakOutCurrentVersion.h"
#include "FrakOutDataTableWidget.h"
#include "FrakOutDrawType.h"
#include "FrakOutMainWindow.h"
#include "FrakOutUnknownFileTypeException.h"
#include "FrakOutVersion.h"
#include "ImageViewerWidget.h"
#include "Panner.h"
#include "PannerWidget.h"
#include "RecentFiles.h"
#include "RecentFilesController.h"
#include "RecentFilesIOQSettings.h"
#include "RecentFilesViewQMenu.h"
#include "ui_FrakOutMainWindow.h"

FrakOutMainWindow::FrakOutMainWindow(QWidget *parent) :
    QMainWindow(parent),
    ui(new Ui::FrakOutMainWindow)
{
    ui->setupUi(this);
    createDockWindows();
    createToolButtonGroup();
    createRecentFiles();

    m_controller = new FrakOutController(ui->m_graphicsViewWidget, m_dataTableWidget->tableView());
    connect(ui->actionFileOpen, SIGNAL(triggered()), this, SLOT(slotOpen()));
    connect(ui->actionOpenFromFile, SIGNAL(triggered()), this, SLOT(slotNew()));
    connect(ui->actionFileQuit, SIGNAL(triggered()), this, SLOT(slotQuit()));
    connect(ui->actionFileSave, SIGNAL(triggered()), this, SLOT(slotSave()));
    connect(ui->actionFileSaveAs, SIGNAL(triggered()), this, SLOT(slotSaveAs()));
    connect(ui->actionIncreaseCellSize, SIGNAL(triggered()), m_controller, SLOT(slotAddItemBeginning()));
    connect(ui->actionDecreaseCellSize, SIGNAL(triggered()), m_controller, SLOT(slotAddItemEnd()));
    connect(ui->actionDeleteSheet, SIGNAL(triggered()), m_controller, SLOT(slotDeleteSheet()));
    connect(ui->actionEraseCells, SIGNAL(triggered()), m_controller, SLOT(slotClearCellsFromCurrentSheet()));
    connect(ui->actionAbout, SIGNAL(triggered()), this, SLOT(slotAbout()));
    connect(m_controller, SIGNAL(signalCurrentSheetChanged(int)), this, SLOT(slotCurrentSheetChanged(int)));

    ui->actionFileOpen->setShortcut(QKeySequence::Open);
    ui->actionFileSave->setShortcut(QKeySequence::Save);
    ui->actionFileSaveAs->setShortcut(QKeySequence::SaveAs);
    ui->actionZoomIn->setShortcut(QKeySequence::ZoomIn);
    ui->actionZoomOut->setShortcut(QKeySequence::ZoomOut);
    ui->actionDeleteSheet->setShortcut(QKeySequence::Delete);

    QSignalMapper *zoomButtonSignalMapper = new QSignalMapper(this);
    connect(ui->actionView50, SIGNAL(triggered()), zoomButtonSignalMapper, SLOT(map()));
    zoomButtonSignalMapper->setMapping(ui->actionView50, 50);
    connect(ui->actionView100, SIGNAL(triggered()), zoomButtonSignalMapper, SLOT(map()));
    zoomButtonSignalMapper->setMapping(ui->actionView100, 100);
    connect(ui->actionView200, SIGNAL(triggered()), zoomButtonSignalMapper, SLOT(map()));
    zoomButtonSignalMapper->setMapping(ui->actionView200, 200);
    connect(ui->actionView300, SIGNAL(triggered()), zoomButtonSignalMapper, SLOT(map()));
    zoomButtonSignalMapper->setMapping(ui->actionView300, 300);
    connect(ui->actionView400, SIGNAL(triggered()), zoomButtonSignalMapper, SLOT(map()));
    zoomButtonSignalMapper->setMapping(ui->actionView400, 400);
    connect(ui->actionView500, SIGNAL(triggered()), zoomButtonSignalMapper, SLOT(map()));
    zoomButtonSignalMapper->setMapping(ui->actionView500, 500);
    connect(zoomButtonSignalMapper, SIGNAL(mapped(int)), this, SLOT(slotZoomChanged(int)));

    connect(ui->actionZoomIn, SIGNAL(triggered()), this, SLOT(slotZoomIn()));
    connect(ui->actionZoomOut, SIGNAL(triggered()), this, SLOT(slotZoomOut()));

    // Setup undo / redo actions.
    QAction *undoAction = m_controller->undoStack()->createUndoAction(this);
    undoAction->setShortcut(QKeySequence::Undo);
    undoAction->setIcon(QIcon(":/icons/icons/arrow_undo.png"));
    undoAction->setStatusTip(tr("Undo the last action"));
    // TODO: Need to add to the toolbar.
    ui->menuEdit->addAction(undoAction);

    QAction *redoAction = m_controller->undoStack()->createRedoAction(this);
    redoAction->setShortcut(QKeySequence::Redo);
    redoAction->setIcon(QIcon(":/icons/icons/arrow_redo.png"));
    redoAction->setStatusTip(tr("Redo the last action"));
    // TODO: Need to add to the toolbar.
    ui->menuEdit->addAction(redoAction);

    ui->actionIncreaseCellSize->setVisible(false);

    connect(m_controller->undoStack(), SIGNAL(cleanChanged(bool)), this, SLOT(slotUndoStackCleanChanged(bool)));

    ui->actionDeleteSheet->setEnabled(false);

    updateWindowTitle();

#ifdef CJR_OLD
    connect(ui->m_imageViewerWidget, SIGNAL(signalSizeChanged(QSize)),
            m_pannerWidget, SLOT(slotSizeChanged(QSize)));
#endif
}

FrakOutMainWindow::~FrakOutMainWindow()
{
    delete ui;
    delete m_controller;
}

void FrakOutMainWindow::changeEvent(QEvent *e)
{
    QMainWindow::changeEvent(e);
    switch (e->type()) {
    case QEvent::LanguageChange:
        ui->retranslateUi(this);
        break;
    default:
        break;
    }
}

void FrakOutMainWindow::closeEvent(QCloseEvent *e)
{
    if (isWindowModified())
    {
        int ret = showCheckSaveChangesMessageBox();

        switch (ret)
        {
        case QMessageBox::Save:
            slotSave();
            e->accept();
            break;

        case QMessageBox::Discard:
            e->accept();
            break;

        case QMessageBox::Cancel:
            e->ignore();
           break;

        default:
           break;
        }
    }
    else
        e->accept();

    // Save the recent files list.
    m_recentFilesController->save();
}

void FrakOutMainWindow::createRecentFiles()
{
    // Set up the recent files list using QSettings for the persistent store.
    RecentFilesIOQSettings recentFilesIO;
    m_recentFiles = new RecentFiles(this);
    m_recentFiles->setIO(&recentFilesIO);

    // Set up a menu as a view of the recent files framework.
    m_recentFilesView = new RecentFilesViewQMenu(*m_recentFiles, ui->menuFile);
    m_recentFilesController = new RecentFilesController(*m_recentFiles, *m_recentFilesView, this);
    m_recentFilesView->setTitle(tr("Recent Files"));
    m_recentFilesView->registerObserver(m_recentFilesController);

    // Insert the recent files menu into the File menu.
    ui->menuFile->insertMenu(ui->actionFileSave, m_recentFilesView);

    // Read the recent files.
    m_recentFilesController->load();

    connect(m_recentFilesController, SIGNAL(signalRecentFileSelected(QString)),
            this, SLOT(slotRecentFileSelected(QString)));
}

int FrakOutMainWindow::showCheckSaveChangesMessageBox()
{
    QMessageBox msgBox;
    msgBox.setText(tr("The document has been modified."));
    msgBox.setInformativeText(tr("Do you want to save your changes?"));
    msgBox.setStandardButtons(QMessageBox::Save | QMessageBox::Discard | QMessageBox::Cancel);
    msgBox.setDefaultButton(QMessageBox::Save);
    return msgBox.exec();
}

void FrakOutMainWindow::createDockWindows()
{
#ifdef CJR_OLD
    QDockWidget *dock = new QDockWidget(tr("Panner"), this);

    dock->setAllowedAreas(Qt::LeftDockWidgetArea | Qt::RightDockWidgetArea);
    m_pannerWidget = new Panner(dock, ui->m_imageViewerWidget->size());
    dock->setWidget(m_pannerWidget);
    dock->setFloating(true);

    connect(m_pannerWidget, SIGNAL(signalRectangleChanged(const QRect&)),
            ui->m_imageViewerWidget, SLOT(slotPannerRectangleChanged(const QRect&)));
#endif
    QDockWidget *dock = new QDockWidget(tr("Sheets"), this);
    m_dataTableWidget = new FrakOutDataTableWidget(dock);

    dock->setAllowedAreas(Qt::LeftDockWidgetArea | Qt::RightDockWidgetArea);
    dock->setWidget(m_dataTableWidget);
    addDockWidget(Qt::RightDockWidgetArea, dock);
}

void FrakOutMainWindow::createToolButtonGroup()
{
    QActionGroup *toolButtonGroup = new QActionGroup(this);

    toolButtonGroup->setExclusive(true);
    toolButtonGroup->addAction(ui->actionFillCell);
    toolButtonGroup->addAction(ui->actionEraseCell);

    connect(ui->actionFillCell, SIGNAL(triggered()), this, SLOT(slotFillCellSelected()));
    connect(ui->actionEraseCell, SIGNAL(triggered()), this, SLOT(slotRemoveCellSelected()));
}

void FrakOutMainWindow::newFile()
{
    QString filename = QFileDialog::getOpenFileName(this, tr("Open an image file"), "/home",
                                 tr("Images (*.png *.jpg *.bmp)"));
    if (!filename.isEmpty())
    {
#ifdef CJR_OLD
        QImage image;
        bool status = image.load(filename);

        if (status)
        {
            ui->m_imageViewerWidget->setImage(image);
            m_pannerWidget->setImage(image);
            m_pannerWidget->slotSizeChanged(ui->m_imageViewerWidget->size());
        }
#else
        m_controller->newFromFile(filename);
        updateWindowTitle();
#endif
    }
}

void FrakOutMainWindow::open()
{
    QString filename = QFileDialog::getOpenFileName(this, tr("Open a fracalc file"), "/home",
                                 QString(tr("FrakOut file (*%1)")).arg(FRAKOUT_FILE_EXT));
    if (!filename.isEmpty())
    {
        try
        {
            if (m_controller->openFrakOutFile(filename))
            {
                updateWindowTitle();
                m_recentFilesController->onOpen(filename);
            }
            else
            {
                QMessageBox::warning(this, tr("File Open Error"),
                                     QString(tr("The file '%1'' cannot be opened.")).arg(filename).toAscii(),
                                     QMessageBox::Ok);
            }
        }
        catch (FrakOutUnknownFileTypeException e)
        {
            QMessageBox::warning(this, tr("Unknown File Type"),
                                 QString(tr("The file '%1'' is not a FrakOut! file.")).arg(filename).toAscii(),
                                 QMessageBox::Ok);
        }
    }
}

void FrakOutMainWindow::slotAbout()
{
    const QString& versionString = FrakOutCurrentVersion::instance()->version()->versionString();
    QMessageBox::about(this, tr("About FrakOut!"),
                       QString(tr("<h2>FrakOut! Version %1</h2>"
                                  "<p>Copyright &copy; 2011 <a href=\"http://www.cjr.me.uk\">cjr.me.uk</a>"
                                  "<p>A utility for calculating the fractal dimension "
                                  "using the box counting method."
                                  "<p><i>Toolbar icons by <a href=\"http://www.famfamfam.com/lab/icons/silk\">FAMFAMFAM</a>")).arg(versionString));
}

void FrakOutMainWindow::slotCurrentSheetChanged(int sheetIndex)
{
    bool enable = m_controller->numSheets() > 1 && (sheetIndex == 0 || sheetIndex == m_controller->numSheets() - 1);
    ui->actionDeleteSheet->setEnabled(enable);
}

void FrakOutMainWindow::slotFillCellSelected()
{
    m_controller->setTool(FrakOutDrawType::Fill());
}

void FrakOutMainWindow::slotNew()
{
    if (isWindowModified())
    {
        int ret = showCheckSaveChangesMessageBox();

        switch (ret)
        {
        case QMessageBox::Save:
            slotSave();
            newFile();
            break;

        case QMessageBox::Discard:
            newFile();
            break;

        case QMessageBox::Cancel:
           break;

        default:
           break;
        }
    }
    else
        newFile();
}

void FrakOutMainWindow::slotOpen()
{
    if (isWindowModified())
    {
        int ret = showCheckSaveChangesMessageBox();

        switch (ret)
        {
        case QMessageBox::Save:
            slotSave();
            open();
            break;

        case QMessageBox::Discard:
            open();
            break;

        case QMessageBox::Cancel:
           break;

        default:
           break;
        }
    }
    else
        open();
}

void FrakOutMainWindow::slotQuit()
{
    close();
}

void FrakOutMainWindow::slotRecentFileSelected(const QString& filename)
{
    bool openFile = false;

    if (isWindowModified())
    {
        int ret = showCheckSaveChangesMessageBox();
        switch (ret)
        {
        case QMessageBox::Save:
            openFile = true;
            slotSave();
            break;

        case QMessageBox::Discard:
            openFile = true;
            break;

        default:
            break;
        }
    }
    else
    {
        openFile = true;
    }

    try
    {
        if (openFile)
        {
            if (m_controller->openFrakOutFile(filename))
            {
                updateWindowTitle();
                m_recentFilesController->onOpen(filename);
            }
            else
            {
                QMessageBox::warning(this, tr("File Open Error"),
                                     QString(tr("The file '%1'' cannot be opened.")).arg(filename).toAscii(),
                                     QMessageBox::Ok);
            }
        }
    }
    catch (FrakOutUnknownFileTypeException e)
    {
        QMessageBox::warning(this, tr("Unknown File Type"),
                             QString(tr("The file %1 is not a FrakOut! file.")).arg(filename).toAscii(),
                             QMessageBox::Ok);
    }
}

void FrakOutMainWindow::slotRemoveCellSelected()
{
    m_controller->setTool(FrakOutDrawType::Erase());
}

void FrakOutMainWindow::slotSave()
{
    QString currentFilename = m_controller->currentFilename();

    if (currentFilename.isEmpty())
    {
        slotSaveAs();
    }
    else
    {
        m_controller->saveFrakOutFile(currentFilename);
        m_recentFilesController->onSave(currentFilename);
    }
}

void FrakOutMainWindow::slotSaveAs()
{
    QString filename = QFileDialog::getSaveFileName(this, tr("Save a fracalc file"), "/home",
                                 QString(tr("FrakOut file (*%1)")).arg(FRAKOUT_FILE_EXT));

    if (!filename.endsWith(FRAKOUT_FILE_EXT))
        filename.append(FRAKOUT_FILE_EXT);

    if (m_controller->saveFrakOutFile(filename))
    {
        updateWindowTitle();
        m_recentFilesController->onSave(filename);
    }
    else
    {
        QMessageBox::warning(this, tr("Save Error"), QString(tr("File '%1' could not be saved.")).arg(filename),
                             QMessageBox::Ok);
    }
}

void FrakOutMainWindow::slotUndoStackCleanChanged(bool clean)
{
    setWindowModified(!clean);
    updateWindowTitle();
}

void FrakOutMainWindow::slotZoomChanged(int value)
{
    double zoom = double(value) / 100.0;

    m_controller->setZoomLevel(zoom);
}

void FrakOutMainWindow::slotZoomIn()
{
    m_controller->setZoomLevel(m_controller->zoomLevel() + 0.1);
}

void FrakOutMainWindow::slotZoomOut()
{
    m_controller->setZoomLevel(m_controller->zoomLevel() - 0.1);
}

void FrakOutMainWindow::updateWindowTitle()
{
    setWindowTitle(QString("%1 - %2[*]").arg(QApplication::applicationName(), m_controller->currentFilename()));
}
